#ifdef _WIN32

#include <Windows.h>
#include "core/hle/kernel/memory_types.h"

#define VIRTUAL_BASE 0x10000
#define VIRTUAL_LAST 0x8000000000
#define VIRTUAL_31BIT (1LU << 31)
#define KUSER_DATA_HUGE_PAGE (VIRTUAL_31BIT - Kernel::HugePageSize)

static_assert(VIRTUAL_BASE % Kernel::PageSize == 0);
static_assert(Kernel::HugePageSize % Kernel::PageSize == 0);
static_assert(VIRTUAL_LAST % Kernel::HugePageSize == 0);


unsigned char* virtual_base = (unsigned char*)VIRTUAL_BASE;

unsigned char* virtual_last = (unsigned char*)VIRTUAL_LAST;

static BOOL allocateVirtualMemory(HANDLE process, SIZE_T begin, SIZE_T end, SIZE_T slice) {
    if (VirtualAlloc2(process, (PVOID)begin, end - begin, MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, PAGE_NOACCESS, NULL, 0)) {
        for (SIZE_T i = begin; i < end - slice; i += slice) {
            VirtualFreeEx(process, (LPVOID)i, slice, MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER);
        }
        return TRUE;
    }
    return FALSE;
}

static BOOL allocateVirtualMemory(HANDLE process) {
    return allocateVirtualMemory(process, VIRTUAL_BASE, Kernel::HugePageSize, Kernel::PageSize) &&
           allocateVirtualMemory(process, Kernel::HugePageSize, KUSER_DATA_HUGE_PAGE,
                                 Kernel::HugePageSize) &&
           allocateVirtualMemory(process, KUSER_DATA_HUGE_PAGE,
                                 Kernel::KUSER_SHARED_DATA_BASE.GetValue(), Kernel::PageSize) &&
           allocateVirtualMemory(process, Kernel::KUSER_SHARED_DATA_LAST.GetValue(), VIRTUAL_31BIT,
                                 Kernel::PageSize) &&
           allocateVirtualMemory(process, VIRTUAL_31BIT, VIRTUAL_LAST, Kernel::HugePageSize);
}

int preloader(int argc, char* argv[]) {
    MEMORY_BASIC_INFORMATION info = {};
    VirtualQuery(virtual_base, &info, sizeof(info));
    if (MEM_FREE == info.State &&
        (SIZE_T)(Kernel::KUSER_SHARED_DATA_BASE.GetValue() - VIRTUAL_BASE) > info.RegionSize) {
        return -2;
    }
    if (MEM_RESERVE == info.State) {
        return 0;
    }
    HANDLE process = GetCurrentProcess();
    if (allocateVirtualMemory(process)) {
        return 0;
    }
    for (int i = 0; i < 64; ++i) {
        STARTUPINFOA startInfo = {0};
        PROCESS_INFORMATION processInfo = {0};
        CreateProcessA(argv[0], NULL, NULL, NULL, FALSE, CREATE_SUSPENDED | DETACHED_PROCESS, NULL,
                       NULL, &startInfo, &processInfo);
        if (allocateVirtualMemory(processInfo.hProcess)) {
            ResumeThread(processInfo.hThread);
            return 1;
        }
        TerminateThread(processInfo.hThread, 0);
        TerminateProcess(processInfo.hProcess, 0);
    }
    return -1;
}

#else

int preloader(int argc, char* argv[]) {
    return 0;
}

#endif